﻿using System;
using System.IO;
using System.Threading;
using System.Net.Sockets;
using System.Net;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.InteropServices;

namespace ECE454P1
{

    public enum message_type { data, message }

    [Serializable]
    class ipc_message
    {
        public byte[] ip_source;
        public byte[] message_type;
        public byte[] message;
        public byte[] file_name;
        public byte[] number_of_chunks;
        public byte[] chunk_number;


        public ipc_message()
        {
            this.ip_source = Encoding.UTF8.GetBytes("".PadRight(256));
            this.message_type = Encoding.UTF8.GetBytes("".PadRight(256));
            this.message = Encoding.UTF8.GetBytes("".PadRight(256));
            this.file_name = Encoding.UTF8.GetBytes("".PadRight(256));
            this.number_of_chunks = Encoding.UTF8.GetBytes("".PadRight(256));
            this.chunk_number = Encoding.UTF8.GetBytes("".PadRight(256));
        }

    }

    class MainClass
    {
        public Peers peers;

        public static void Main(string[] args)
        {
            new TcpListen();
/*
            Status status = new Status();

            status.numberOfFiles();

            Peer local_peer;
            Peers peers = new Peers();

            peers.initialize(@"peers.txt");
            local_peer = peers.peers[0];

            
            foreach (Peer p in peers.peers)
            {
                p.join();
            }
            

            //local_peer.insert(@"test.zip");
            local_peer.send_file(@"test.zip");
            */
            while (true)
            {
                Thread.Sleep(1);
            }
        }
    }

    class TcpListen
    {
        private TcpListener server;
        private Thread server_thread;

        public TcpListen()
        {
            this.server = new TcpListener(IPAddress.Any, 10030);
            this.server_thread = new Thread(new ThreadStart(listen_thread));
            this.server_thread.Start();
        }

        public void listen_thread()
        {
            this.server.Start();
            while (true)
            {
                TcpClient client = this.server.AcceptTcpClient();
                Console.WriteLine("TCP connection accepted");

                Thread client_thread = new Thread(new ParameterizedThreadStart(communication_thread));
                client_thread.Start(client);
            }
        }

        private void communication_thread(object tcp_client)
        {
            TcpClient client = (TcpClient)tcp_client;
            NetworkStream stream = client.GetStream();
            ipc_message header_message = new ipc_message();

            int i = 0;
            Byte[] r_message = new Byte[Constants.chunkSize];
            BinaryFormatter formatter = new BinaryFormatter();

            //Read header


            i = stream.Read(r_message, 0, r_message.Length);

            Console.WriteLine("SERVER: Bytes Read:{0}", i);


            Console.WriteLine("SERVER: Read Message");

            MemoryStream ms = new MemoryStream(r_message);

            Console.WriteLine(header_message.GetType().AssemblyQualifiedName);
            object o = formatter.Deserialize(ms);
            Console.WriteLine(o.GetType().AssemblyQualifiedName);

            header_message = (ipc_message)formatter.Deserialize(ms);

            Console.WriteLine("SERVER: byte gonna send");
            stream.WriteByte(00);
            Console.WriteLine("SERVER: byte sent");
            //Message type
            if (Encoding.UTF8.GetString(header_message.message_type).TrimEnd() == message_type.message.ToString())
            {
                Console.WriteLine("message type:{0}", Encoding.UTF8.GetString(header_message.message));
            }
            //Data type
            else if (Encoding.UTF8.GetString(header_message.message_type).TrimEnd() == message_type.data.ToString())
            {
                Console.WriteLine("data type");
                FileStream file_stream = new FileStream("files/" + Encoding.UTF8.GetString(header_message.file_name).Trim(), FileMode.Create, FileAccess.ReadWrite);

                while ((i = stream.Read(r_message, 0, r_message.Length)) != 0)
                {
                    file_stream.Write(r_message, 0, i);
                    Console.WriteLine("SERVER: Recieved:{0}", i);
                }
                Console.WriteLine("SERVER: Finished Receiving file", i);
                file_stream.Close();
            }
            client.Close();
            return;
        }
    }

    // Peer and Status are the classes we really care about
    // Peers is a container; feel free to do a different container
    class Peer
    {
        public uint port;
        public IPAddress ipaddress;

        private TcpClient _client;
        private NetworkStream _network_stream;
        private bool _is_connected = false;

        private Peers _peers;

        public int insert(string fileName)
        {
            if (File.Exists(fileName))
            {
                FileInfo file = new FileInfo(fileName);
                File.Copy(fileName, "files/" + file.Name, true);
                return Constants.errOK;
            }

            return Constants.errUnknownFatal;
        }

        public int query(Status status)
        {
            return status.populate(this);
        }

        public int join()
        {
            if (!is_connected())
            {
                connect();
            }
            ipc_message header_message = new ipc_message();

            header_message.ip_source = Encoding.UTF8.GetBytes(ipaddress.ToString().PadRight(256));
            header_message.message_type = Encoding.UTF8.GetBytes(message_type.message.ToString().PadRight(256));
            header_message.message = Encoding.UTF8.GetBytes("JOIN".PadRight(256));

            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(_network_stream, header_message);

            disconnect();

            return Constants.errUnknownFatal; // Unknown error
        }

        public int leave()
        {
            return -1; // TODO
        }

        public int send_file(string file)
        {
            if (!is_connected())
            {
                connect();
            }
            ipc_message header_message = new ipc_message();
            Byte[] chunk = new byte[Constants.chunkSize];

            header_message.ip_source = Encoding.UTF8.GetBytes(ipaddress.ToString().PadRight(256));
            header_message.message_type = Encoding.UTF8.GetBytes(message_type.data.ToString().PadRight(256));
            header_message.file_name = Encoding.UTF8.GetBytes(file.PadRight(256));

            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(_network_stream, header_message);

            Console.WriteLine("CLIENT: Waiting for Byte");
            _network_stream.ReadByte();
            Console.WriteLine("CLIENT: Byte Received");

            FileStream file_stream = new FileStream(file, FileMode.Open);

            int i = 0;
            Console.WriteLine("CLIENT: Sending File");
            while ((i = file_stream.Read(chunk, 0, Constants.chunkSize)) != 0)
            {
                _network_stream.Write(chunk, 0, i);
                Console.WriteLine("CLIENT: Chunk:{0}", i);
            }
            Console.WriteLine("CLIENT: File Sent");
            file_stream.Close();
            disconnect();

            return 1;
        }

        private bool is_connected()
        {
            return _is_connected;
        }

        private int disconnect()
        {
            _network_stream.Close();
            _client.Close();
            _is_connected = false;

            return 1;
        }

        private int connect()
        {
            try
            {
                _client = new TcpClient(ipaddress.ToString(), (int)port);
                _network_stream = _client.GetStream();
                _is_connected = true;
                return Constants.errOK;
            }
            catch (ArgumentException e)
            {
                Console.WriteLine("Argument Exception: {0}", e);
                return Constants.errCannotConnect; // Cannot connect to anything; fatal error
            }
            catch (SocketException e)
            {
                Console.WriteLine("Socket Exception: {0}", e);
                return Constants.errCannotConnect; // Cannot connect to anything; fatal error
            }
        }
    }

    // Status is the class that you populate with status data on the state
    // of replication in this peer and its knowledge of the replication
    // level within the system.
    // The thing required in the Status object is the data as specified in the private section
    // The methods shown are examples of methods that we may implement to access such data
    // You will need to create methods to populate the Status data.
    class Status
    {
        private int _numFiles;                                                          // The number of files currently in the system, as viewed by this peer
        private float[] _local = new float[Constants.maxFiles];                         // The fraction of the file present locally 
        // (= chunks on this peer/total number chunks in the file)
        private float[] _system = new float[Constants.maxFiles];                        // The fraction of the file present in the system 
        // (= chunks in the system/total number chunks in the file)
        // (Note that if a chunk is present twice, it doesn't get counted twice;
        // this is simply intended to find out if we have the whole file in
        // the system; given that a file must be added at a peer, think about why
        // this number would ever not be 1.)
        private int[] _leastReplication = new int[Constants.maxFiles];                  // Sum by chunk over all peers; the minimum of this number is the least 
        // replicated chunk, and thus represents the least level of replication
        // of the file
        private float[] _weightedLeastReplication = new float[Constants.maxFiles];      // Sum all chunks in all peers; divde this by the number of chunks in the
        // file; this is the average level of replication of the file

        private FileInfo[] getFiles()
        {
            return new DirectoryInfo("files").GetFiles();
        }

        public Status()
        {
            int i = 0;
            foreach (FileInfo file in getFiles())
            {
                _local[i] = 1;
                i++;
            }
        }

        public int populate(Peer peer)
        {
            // TODO
            return Constants.errOK;
        }

        public int numberOfFiles()
        {
            _numFiles = getFiles().Length;
            Console.WriteLine("Number of files: " + _numFiles);
            return _numFiles;
        }

        // Use -1 to indicate if the file requested is not present
        public float fractionPresentLocally(int fileNumber)
        {
            if (fileNumber >= numberOfFiles())
            {
                return Constants.errUnknownFatal;
            }
            return _local[fileNumber];
        }

        // Use -1 to indicate if the file requested is not present in the system
        public float fractionPresent(int fileNumber)
        {
            if (fileNumber >= numberOfFiles())
            {
                return Constants.errUnknownFatal;
            }
            return _system[fileNumber];
        }

        // Use -1 to indicate if the file requested is not present in the system
        public int minimumReplicationLevel(int fileNumber)
        {
            if (fileNumber >= numberOfFiles())
            {
                return Constants.errUnknownFatal;
            }
            return _leastReplication[fileNumber];
        }

        // Use -1 to indicate if the file requested is not present in the system
        public float averageReplicationLevel(int fileNumber)
        {
            if (fileNumber >= numberOfFiles())
            {
                return Constants.errUnknownFatal;
            }
            return _weightedLeastReplication[fileNumber];
        }
    }


    // Peers is a dumb container to hold the peers; the number of peers is fixed,
    // but needs to be set up when a peer starts up; feel free to use some other
    // container class, but that class must have a method that allows it to read
    // the peersFile, since otherwise you have no way of having a calling entity 
    // tell your code what the peers are in the system.
    class Peers
    {
        public List<Peer> peers = new List<Peer>();
        private int _numPeers = 0;

        // The peersFile is the name of a file that contains a list of the peers
        // Its format is as follows: in plaintext there are up to maxPeers lines,
        // where each line is of the form: <IP address> <port number>
        // This file should be available on every machine on which a peer is started,
        // though you should exit gracefully if it is absent or incorrectly formatted.
        // After execution of this method, the _peers should be present.
        public int initialize(string peersFile)
        {
            if (File.Exists(peersFile))
            {
                try
                {
                    StreamReader file = File.OpenText(peersFile);
                    string s = "";
                    string[] server;

                    while ((s = file.ReadLine()) != null)
                    {
                        server = s.Split(',');
                        try
                        {
                            Peer peer = new Peer();
                            peer.ipaddress = IPAddress.Parse(server[0]);
                            peer.port = uint.Parse(server[1]);

                            peers.Add(peer);
                            _numPeers++;
                        }
                        catch (Exception e)
                        {
                            Console.WriteLine("PEERS: IP Address Exception: {0}", e);
                        }
                    }

                    file.Close();
                }
                catch (Exception e)
                {
                    Console.WriteLine("PEERS: File Open Exception: {0}", e);
                }
            }
            else
            {
                Console.WriteLine("File does not exist");
                return Constants.errUnknownFatal;
            }
            return Constants.errUnknownFatal;
        }
    }

    class Constants
    {
        public const int maxPeers = 6;
        public const int chunkSize = 65536;
        public const int maxFiles = 100;

        public const int errOK = 0; // Everything good
        public const int errUnknownWarning = 1; // Unknown warning
        public const int errUnknownFatal = -2; // Unknown error
        public const int errCannotConnect = -3; // Cannot connect to anything; fatal error
        public const int errNoPeersFound = -4; // Cannot find any peer (e.g., no peers in a peer file); fatal
        public const int errPeerNotFound = 5; // Cannot find some peer; warning, since others may be connectable
    }
}
